#!/usr/bin/python3
# coding=utf8
import redis
# from rediscluster import StrictRedisCluster
import traceback


class RedisHandler(object):
    def __init__(self, host="localhost", port=6379, db=0, startup_node={}, startup_nodes=[]):
        """
        Redis连接类，其中包含单机模式和集群模式两种连接方法
        :param startup_nodes: 优先读取，是否为集群模式
        :param startup_node: 非集群模式后，优先读取连接对象
        :param host: 指定Host（若以上两个字段都为空，该值才被使用）
        :param port: 指定端口（只能是数字）
        :param db: 指定库（只能是0-15的数字）
        """
        self.client = None

        self.__init_connect__(host=host, port=port, db=db, startup_node=startup_node, startup_nodes=startup_nodes)

    def __init_connect__(self, host="localhost", port=6379, db=0, startup_node={}, startup_nodes=[]):
        if startup_nodes:
            nodes = []
            for startup_node in startup_nodes:
                if not isinstance(startup_node, dict):
                    raise LinkParameterException("startup_nodes字段中的数据必须是dict类型。{}".format(startup_node))

                if "host" not in startup_node:
                    raise LinkParameterException("Redis连接参数中不可缺少host。{}".format(startup_node))

                if "port" not in startup_node:
                    raise LinkParameterException("Redis连接参数中不可缺少port。{}".format(startup_node))

                if "db" in startup_node or "database" in startup_node:
                    raise LinkParameterException("Redis集群模式不可指定db。{}".format(startup_node))

                nodes.append(startup_node)

            self.__connects__(nodes)
        else:
            if startup_node:
                host = startup_node["host"]
                port = startup_node["port"]
                database = startup_node["database"] if startup_node.get("database", None) else startup_node.get("db",
                                                                                                                None)
                if database:
                    db = database

            if not isinstance(port, int) and not port.isdigit():
                raise LinkParameterException("port字段只能是数字。")

            if not isinstance(db, int) and not db.isdigit():
                raise LinkParameterException("db字段只能是数字。")

            self.__connect__(host, port, int(db))

    def __connect__(self, host, port, db):
        self.client = redis.Redis(host=host, port=port, db=db)

    def __connects__(self, nodes):
        self.client = StrictRedisCluster(startup_nodes=nodes)

    def __close__(self):
        """当前版本不支持关闭连接，所以不需要关闭"""

    def info(self, israise=True):
        """获取指redis的基本信息"""
        if israise:
            return self.client.info()

        try:
            return self.client.info()
        except:
            print(traceback.format_exc())

        return None

    def keys(self):
        return self.client.keys()

    def type(self, name):
        return self.client.type(name)

    def hlen(self, name):
        """获取指定Name中包含数据的个（行）数"""
        return self.client.hlen(name)

    def hset(self, name=None, key=None, value=None):
        """根据Name和Key设置数据"""
        self.client.hset(name=name, key=key, value=value)

    def hget(self, name=None, key=None):
        """根据Name和Key获取数据"""
        return self.client.hget(name=name, key=key)

    def hdel(self, name, *keys):
        """根据Name和Key删除数据"""
        return self.client.hdel(name, *keys)

    def hexists(self, name, key):
        """根据Name和Key删除数据"""
        return self.client.hexists(name, key)

    def hkeys(self, name):
        """根据Name和Key删除数据"""
        return self.client.hkeys(name)

    def set(self, key, value, expire=None):
        """
        根据key设置Value
        :param expire: 设置该节点自动释放的时间，以秒为单位，默认为永久
        :return:
        """
        self.client.set(key, value)
        if expire:
            self.expire(key, expire)

    def get(self, key):
        """根据key获取数据"""
        return self.client.get(key)

    def expire(self, key, expire):
        """
        为指定节点设置自动释放时间，以秒为单位。
        """
        if not expire:
            raise OperationParameterException("expire字段不可为空 .")

        if not isinstance(expire, int) and not expire.isdigit():
            raise Exception("expire字段必须为数字类型 .")

        self.client.expire(key, int(expire))

    def delete(self, *keys):
        """删除一些节点"""
        self.client.delete(*keys)

    def hgetall(self, name):
        """"""
        return self.client.hgetall(name)

    def blpop(self, name):
        """队列模式，从左侧取出数据"""
        return self.client.blpop(name)

    def lpop(self, name):
        """队列模式，从左侧取出数据"""
        return self.client.lpop(name)

    def rpop(self, name):
        """队列模式，从右侧取出数据"""
        return self.client.rpop(name)

    def lpush(self, name, *values):
        """队列模式，从左侧放入数据"""
        return self.client.lpush(name, *values)

    def rpush(self, name, *values):
        """队列模式，从右侧放入数据"""
        return self.client.rpush(name, *values)

    def lrange(self, name, start, end):
        """队列模式，遍历队列，从start到end，不会对数据做更改"""
        return self.client.lrange(name, start, end)

    def llen(self, name):
        """队列模式，获取队列长度"""
        return self.client.llen(name)

    def pipeline(self, transaction=True, shard_hint=None):
        return self.client.pipeline(transaction=transaction, shard_hint=shard_hint)

    def rename(self, src, dst):
        return self.client.rename(src, dst)

    def srandmember(self, name, number=None):
        return self.client.srandmember(name, number=number)

    def sadd(self, name, *values):
        return self.client.sadd(name, *values)

    def srem(self, name, *values):
        return self.client.srem(name, *values)

    def smembers(self, name):
        return self.client.smembers(name)


class RedisException(Exception):
    """ redis相关的异常，会追加异常内容 """

    def __init__(self, value=None):
        if value:
            self.value = self.__doc__ + value
        else:
            self.value = self.__doc__

    def __str__(self):
        return repr(self.value)


# class Redis2Exception(Exception):
#     """ redis相关的异常 """
#
#     def __init__(self, value=None):
#         if value:
#             self.value = value
#         else:
#             self.value = self.__doc__
#
#     def __str__(self):
#         return repr(self.value)


class ParentMethodCallException(RedisException):
    """ Redis服务连接参数异常 """


class LinkParameterException(RedisException):
    """ Redis服务连接参数异常 """


class OperationParameterException(RedisException):
    """ 操作Redis时参数异常 """


if __name__ == "__main__":
    # 单机模式创建连接
    redishandler = RedisHandler(startup_node={"host": "10.10.202.36", "port": 6379, "database": 1})
    # 集群模式创建连接
    # redishandler = RedisHandler(startup_nodes=[
    #     {"host": "10.255.73.153", "port": 7001},
    #     {"host": "10.255.73.154", "port": 7002},
    # ])

    # 普通的Key-Value存储使用示例
    redishandler.set("test:532727197507066961", "韩白梅", expire=30)
    print("get: " + redishandler.get("test:532727197507066961").decode())
    redishandler.delete("test:532727197507066961")
    print("*********************************************")

    # 队列模式的使用，push，pop，delete
    redishandler.lpush("test:queue", "532727197507066961", "622700197906013762")
    redishandler.rpush("test:queue", "431002198308080343", "510303197505187438")
    # 获取队列长度
    print("llen: {}".format(redishandler.llen("test:queue")))
    print("lpop: {}".format(redishandler.lpop("test:queue").decode()))
    print("rpop: {}".format(redishandler.rpop("test:queue").decode()))
    # 遍历队列
    for line in redishandler.lrange("test:queue", 0, 10):
        print("lrange: {}".format(line.decode()))
    # 删除节点
    redishandler.delete("test:queue")
    print("*********************************************")

    # Name-Key-Value存储使用
    redishandler.hset(name="test:h", key="510303197505187438", value="汤翰林")
    redishandler.hset(name="test:h", key="120102197506253003", value="徐尔安")
    redishandler.hset(name="test:h", key="320323197002059370", value="戴博简")
    print("hget: {}".format(redishandler.hget(name="test:h", key="320323197002059370").decode()))
    # 获取当前Name下有多少个Key
    print("hgetall: {}".format(redishandler.hgetall("test:h")))
    # 删除指定Name下的指定Key
    redishandler.hdel("test:h", "320323197002059370")
    print("hgetall: {}".format(redishandler.hgetall("test:h")))
    # 删除指定节点
    redishandler.delete("test:h")
    print("*********************************************")

    # 事务的使用方法
    try:
        pipe = redishandler.pipeline()
        pipe.set("aa", "123")
        pipe.sadd('s001', 'a')

        # sys.exit()
        raise Exception("手动停止退出。")

        # 在事务还没有提交前退出，所以事务不会被执行。
        pipe.sadd('s001', 'b')
        pipe.execute()
    except Exception as err:
        print(err)
    print("*********************************************")
